#include <malloc.h>
#include <errno.h>
#include <memory.h>
#include "type.h"
#include "mapper.h"
#include "ppu.h"
#include "cpu.h"
#include "bus.h"
#include "util.h"

static bool ines_console_init(NesConsole *console, const uint8 *buf) {
    //Check nes rom format prefix
    if (buf[0] != 'N' || buf[1] != 'E' || buf[2] != 'S') {
        errno = UNKNOWN_ROM_FILE_FORMAT;
        return False;
    }
    //
    // An iNES file consists of the following sections, in order:
    //
    // Header (16 bytes)
    // Trainer, if present (0 or 512 bytes)
    // PRG ROM data (16384 * x bytes)
    // CHR ROM data, if present (8192 * y bytes)
    // PlayChoice INST-ROM, if present (0 or 8192 bytes)
    // PlayChoice PROM, if present (16 bytes Data, 16 bytes CounterOut) (this is often missing; see PC10 ROM-Images for details)
    //
    uint8 f6 = buf[6];
    uint8 f7 = buf[7];

    Mapper mapper = (f6 >> 4) | f7 & 0xf0;
    if (mapper > UN_SUPPORT) {
        errno = UNKNOWN_CARTRIDGE_BRAND;
        return False;
    }

    int prgSize = buf[4] * 16 * 1024;
    int chrSize = buf[5] * 8 * 1024;
    console->mirroring = (Mirroring) (f6 & 0x01);
    console->region = (NesRegion) (buf[9] & 0x01);
    NesFormat format = (f7 >> 2 & 0x03) == 2 ? NES_20 : INES;

    int offset = 16;

    uint8 *chrRom = NULL;
    uint8 *prgRom = NULL;

    //512-byte trainer at $7000-$71FF (stored before PRG data)
    if (((f6 >> 2) & 0x01) == 0x01) {
        offset += 512;
        uint8 trainer[512];
        memcpy(trainer, buf + offset, 512);
        ines_bus_sram_fill(console->bus, trainer, 0x1000, 512);
    }
    prgRom = ines_new_array(prgSize);
    chrRom = ines_new_array(chrSize);

    memcpy(prgRom, buf + offset, prgSize);
    offset += prgSize;

    if (chrSize > 0) {
        memcpy(chrRom, buf + offset, chrSize);
    }

    console->masterCycle = 0;
    console->mapper = mapper;
    console->format = format;
    console->chrRom = chrRom;
    console->prgRom = prgRom;
    console->chrSize = chrSize;
    console->prgSize = prgSize;

    switch (mapper) {
        case NROM:
            ines_NROM_register(console);
            break;
        default:
            //todo
    }

    return True;
}

extern NesConsole *ines_console_new(uint8 *buf, InesGameCallback callback) {
    NesConsole *console = malloc(sizeof(NesConsole));

    console->prgSize = 0;
    console->chrSize = 0;
    console->callback = callback;
    console->cpu = ines_cpu_new();
    console->bus = ines_bus_new();
    console->ppu = ines_ppu_new();
    console->cartridge = malloc(sizeof(CartridgeMapper));

    bool success = ines_console_init(console, buf);
    //Console instance fail dispose instance
    if (!success) {
        ines_console_dispose(&console);
    }
    return console;
}

extern void ines_console_dispose(NesConsole **console) {
    if (console == NULL) {
        return;
    }
    NesConsole *tmp = *console;
    if (tmp->prgSize) {
        free(tmp->prgRom);
    }
    if (tmp->chrSize) {
        free(tmp->chrRom);
    }
    if (tmp->cartridge != NULL) {
        free(tmp->cartridge);
    }

    ines_cpu_dispose(tmp->cpu);
    ines_bus_dispose(tmp->bus);
    ines_ppu_dispose(tmp->ppu);

    free(tmp);
    *console = NULL;
}

extern uint64 ines_console_master_cycle(NesConsole *console) {
    return console->masterCycle;
}